좋은 단위 테스트는 무엇입니까? [닫은]
저는 여러분 대부분이 자동화 된 테스트를 많이 작성하고 있으며 단위 테스트시 일반적인 함정에 부딪혔다 고 확신합니다.
내 질문은 미래의 문제를 피하기 위해 테스트 작성에 대한 행동 규칙을 따르나요? 더 구체적으로 말하면 , 좋은 단위 테스트 의 속성은 무엇입니까 ? 아니면 테스트를 어떻게 작성합니까?
언어에 구애받지 않는 제안이 권장됩니다.
소스를 연결하는 것으로 시작하겠습니다 -JUnit을 사용하는 Java의 Pragmatic Unit Testing (C # -Nunit도 포함 된 버전도 있지만이 버전도 있습니다. 대부분의 경우 불가지론 적입니다. 권장 됨)
Good Tests는 A TRIP 이어야합니다 (약어가 충분히 끈적 거리지 않습니다. 책에 치트 시트가 인쇄되어있어이 문제가 올바른지 확인해야했습니다 ..)
- 자동 : 테스트 호출 및 PASS / FAIL 결과 확인이 자동으로 수행되어야합니다.
- 철저한 : 범위; 버그는 코드의 특정 지역을 중심으로 클러스터되는 경향이 있지만 모든 주요 경로와 시나리오를 테스트해야합니다. 테스트되지 않은 지역을 알아야하는 경우 도구를 사용합니다.
- 반복성 : 테스트는 매번 동일한 결과를 생성해야합니다. 매번. 테스트는 제어 할 수없는 매개 변수에 의존해서는 안됩니다.
- 독립 : 매우 중요합니다.
- 테스트는 한 번에 한 가지만 테스트해야합니다 . 여러 주장은 모두 하나의 기능 / 동작을 테스트하는 한 괜찮습니다. 테스트가 실패하면 문제의 위치를 정확히 찾아 내야합니다.
- 테스트 는 서로 의존해서는 안됩니다 .-격리 됨. 테스트 실행 순서에 대한 가정이 없습니다. 설정 / 해체를 적절하게 사용하여 각 테스트 전에 '클린 슬레이트'를 확인하십시오.
전문가 : 장기적으로는 프로덕션만큼 많은 테스트 코드를 갖게되므로 테스트 코드에 대해 동일한 좋은 디자인 표준을 따릅니다. 의도를 드러내는 이름, 중복 없음, 좋은 이름의 테스트 등을 가진 잘 팩터링 된 메서드 클래스
좋은 테스트도 빠르게 실행 됩니다. 실행하는 데 0.5 초 이상 걸리는 모든 테스트를 수행해야합니다. 테스트 스위트를 실행하는 데 시간이 오래 걸릴수록 실행 빈도가 줄어 듭니다. 더 많은 변경 사항이있을수록 개발자는 실행 사이에 몰래 들어 가려고합니다. 어떤 것이 깨지면 .. 어떤 변경이 원인인지 알아내는 데 시간이 더 오래 걸립니다.
2010-08 업데이트 :
- 가독성 : 이것은 Professional의 일부로 간주 될 수 있지만 충분히 강조 할 수는 없습니다. 산성 테스트는 팀의 일원이 아닌 사람을 찾아서 몇 분 안에 테스트중인 행동을 파악하도록 요청하는 것입니다. 테스트는 프로덕션 코드처럼 유지되어야하므로 더 많은 노력이 필요하더라도 쉽게 읽을 수 있도록해야합니다. 테스트는 대칭이어야하며 (패턴을 따름) 간결해야합니다 (한 번에 하나의 동작을 테스트). 일관된 명명 규칙 (예 : TestDox 스타일)을 사용합니다. "부수적 인 세부 사항"으로 테스트를 복잡하게 만들지 마십시오. 미니멀리스트가 되십시오.
이 외에도 대부분의 다른 것들은 저수익 작업을 줄이는 지침입니다. 예 : '소유하지 않은 코드는 테스트하지 마십시오'(예 : 타사 DLL). 게터와 세터를 테스트하지 마십시오. 비용 대 이익 비율 또는 결함 가능성을 주시하십시오.
- 엄청난 테스트를 작성하지 마십시오. '단위 테스트'의 '단위'에서 알 수 있듯이 각 단위 를 가능한 한 원자 적 으로 분리 하십시오. 필요한 경우 일반적인 사용자 환경을 너무 많이 수동으로 다시 만드는 대신 모의 개체를 사용하여 전제 조건을 만듭니다.
- 분명히 작동하는 것을 테스트하지 마십시오. 타사 공급 업체의 클래스, 특히 코딩 한 프레임 워크의 핵심 API를 제공하는 공급 업체의 클래스를 테스트하지 마십시오. 예를 들어 공급 업체의 Hashtable 클래스에 항목을 추가하는 것을 테스트하지 마십시오.
- NCover와 같은 코드 커버리지 도구를 사용하여 아직 테스트하지 않은 엣지 케이스를 찾는 것을 고려하십시오 .
- 구현 하기 전에 테스트 를 작성하십시오 . 테스트를 구현이 준수 할 사양으로 생각하십시오. Cf. 또한 행동 중심 개발, 테스트 중심 개발의보다 구체적인 분기입니다.
- 일관성을 유지하십시오. 일부 코드에 대한 테스트 만 작성한다면 거의 유용하지 않습니다. 팀에서 일하고 다른 일부 또는 모두가 테스트를 작성하지 않으면 그다지 유용하지 않습니다. 자신과 다른 모든 사람에게 테스트 의 중요성 (및 시간 절약 속성)을 설득 하거나 귀찮게하지 마십시오.
여기에있는 대부분의 답변은 실제로 테스트 자체 (어떻게)를 작성하는 것이 아니라 일반적으로 (언제, 어디서, 왜, 무엇을) 단위 테스트 모범 사례를 다루는 것 같습니다. 질문이 "어떻게"부분에 대해 매우 구체적으로 보였기 때문에, 나는 회사에서 실시한 "갈색 가방"프레젠테이션에서 가져온 이것을 게시 할 것이라고 생각했습니다.
Womp의 5 가지 작문 시험 법칙 :
1. 길고 설명적인 테스트 방법 이름을 사용합니다.
- Map_DefaultConstructorShouldCreateEmptyGisMap()
- ShouldAlwaysDelegateXMLCorrectlyToTheCustomHandlers()
- Dog_Object_Should_Eat_Homework_Object_When_Hungry()
2. Arrange / Act / Assert 스타일로 테스트를 작성합니다 .
- 이 조직 전략은 한동안 존재했고 많은 것을 불렀지 만, 최근에 "AAA"두문자어의 도입은이를 전달하는 좋은 방법이었습니다. 모든 테스트를 AAA 스타일과 일관되게 만들면 읽기 및 유지 관리가 쉽습니다.
3. 항상 어설 션과 함께 실패 메시지를 제공하십시오.
Assert.That(x == 2 && y == 2, "An incorrect number of begin/end element
processing events was raised by the XElementSerializer");
- 러너 애플리케이션에서 무엇이 실패했는지를 분명하게하는 간단하면서도 보람있는 연습입니다. 메시지를 제공하지 않으면 일반적으로 실패 출력에 "Expected true, was false"와 같은 메시지가 표시되므로 실제로 테스트를 읽어서 무엇이 잘못되었는지 확인해야합니다.
4. 테스트 이유를 설명하십시오 . 비즈니스 가정은 무엇입니까?
/// A layer cannot be constructed with a null gisLayer, as every function
/// in the Layer class assumes that a valid gisLayer is present.
[Test]
public void ShouldNotAllowConstructionWithANullGisLayer()
{
}
- 이것은 당연한 것처럼 보일 수 있지만이 방법은 처음에 테스트의 이유를 이해하지 못하는 사람들로부터 테스트의 무결성을 보호합니다. 나는 그 사람이 테스트가 검증하고 있다는 가정을 이해하지 못했기 때문에 완벽하게 괜찮은 많은 테스트가 제거되거나 수정되는 것을 보았습니다.
- 테스트가 사소하거나 메서드 이름이 충분히 설명적인 경우 주석을 생략 할 수 있습니다.
5. 모든 테스트는 항상 접촉하는 리소스의 상태를 되돌려 야합니다.
- 실제 자원을 다루지 않도록 가능한 경우 모의를 사용하십시오.
- 테스트 수준에서 정리를 수행해야합니다. 테스트는 실행 순서에 의존하지 않아야합니다.
이러한 목표를 염두에 두십시오 (Meszaros의 xUnit Test Patterns 책에서 수정 됨).
- 테스트는 위험을 도입하는 것이 아니라 감소시켜야합니다.
- 테스트는 실행하기 쉬워야합니다.
- Tests should be easy to maintain as the system evolves around them
Some things to make this easier:
- Tests should only fail because of one reason.
- Tests should only test one thing
- Minimize test dependencies (no dependencies on databases, files, ui etc.)
Don't forget that you can do intergration testing with your xUnit framework too but keep intergration tests and unit tests separate
Tests should be isolated. One test should not depend on another. Even further, a test should not rely on external systems. In other words, test your code, not the code your code depends on.You can test those interactions as part of your integration or functional tests.
Some properties of great unit tests:
When a test fails, it should be immediately obvious where the problem lies. If you have to use the debugger to track down the problem, then your tests aren't granular enough. Having exactly one assertion per test helps here.
When you refactor, no tests should fail.
Tests should run so fast that you never hesitate to run them.
All tests should pass always; no non-deterministic results.
Unit tests should be well-factored, just like your production code.
@Alotor: If you're suggesting that a library should only have unit tests at its external API, I disagree. I want unit tests for each class, including classes that I don't expose to external callers. (However, if I feel the need to write tests for private methods, then I need to refactor.)
EDIT: There was a comment about duplication caused by "one assertion per test". Specifically, if you have some code to set up a scenario, and then want to make multiple assertions about it, but only have one assertion per test, you might duplication the setup across multiple tests.
I don't take that approach. Instead, I use test fixtures per scenario. Here's a rough example:
[TestFixture]
public class StackTests
{
[TestFixture]
public class EmptyTests
{
Stack<int> _stack;
[TestSetup]
public void TestSetup()
{
_stack = new Stack<int>();
}
[TestMethod]
[ExpectedException (typeof(Exception))]
public void PopFails()
{
_stack.Pop();
}
[TestMethod]
public void IsEmpty()
{
Assert(_stack.IsEmpty());
}
}
[TestFixture]
public class PushedOneTests
{
Stack<int> _stack;
[TestSetup]
public void TestSetup()
{
_stack = new Stack<int>();
_stack.Push(7);
}
// Tests for one item on the stack...
}
}
What you're after is delineation of the behaviours of the class under test.
- Verification of expected behaviours.
- Verification of error cases.
- Coverage of all code paths within the class.
- Exercising all member functions within the class.
The basic intent is increase your confidence in the behaviour of the class.
This is especially useful when looking at refactoring your code. Martin Fowler has an interesting article regarding testing over at his web site.
HTH.
cheers,
Rob
Test should originally fail. Then you should write the code that makes them pass, otherwise you run the risk of writing a test that is bugged and always passes.
I like the Right BICEP acronym from the aforementioned Pragmatic Unit Testing book:
- Right: Are the results right?
- B: Are all the boundary conditions correct?
- I: Can we check inverse relationships?
- C: Can we cross-check results using other means?
- E: Can we force error conditions to happen?
- P: Are performance characteristics within bounds?
Personally I feel that you can get pretty far by checking that you get the right results (1+1 should return 2 in a addition function), trying out all the boundary conditions you can think of (such as using two numbers of which the sum is greater than the integer max value in the add function) and forcing error conditions such as network failures.
Good tests need to be maintainable.
I haven't quite figured out how to do this for complex environments.
All the textbooks start to come unglued as your code base starts reaching into the hundreds of 1000's or millions of lines of code.
- Team interactions explode
- number of test cases explode
- interactions between components explodes.
- time to build all the unittests becomes a significant part of the build time
- an API change can ripple to hundreds of test cases. Even though the production code change was easy.
- the number of events required to sequence processes into the right state increases which in turn increases test execution time.
Good architecture can control some of interaction explosion, but inevitably as systems become more complex the automated testing system grows with it.
This is where you start having to deal with trade-offs:
- only test external API otherwise refactoring internals results in significant test case rework.
- setup and teardown of each test gets more complicated as an encapsulated subsystem retains more state.
- nightly compilation and automated test execution grows to hours.
- increased compilation and execution times means designers don't or won't run all the tests
- to reduce test execution times you consider sequencing tests to take reduce set up and teardown
You also need to decide:
where do you store test cases in your code base?
- how do you document your test cases?
- can test fixtures be re-used to save test case maintenance?
- what happens when a nightly test case execution fails? Who does the triage?
- How do you maintain the mock objects? If you have 20 modules all using their own flavor of a mock logging API, changing the API ripples quickly. Not only do the test cases change but the 20 mock objects change. Those 20 modules were written over several years by many different teams. Its a classic re-use problem.
- individuals and their teams understand the value of automated tests they just don't like how the other team is doing it. :-)
I could go on forever, but my point is that:
Tests need to be maintainable.
I covered these principles a while back in This MSDN Magazine article which I think is important for any developer to read.
The way I define "good" unit tests, is if they posses the following three properties:
- They are readable (naming, asserts, variables, length, complexity..)
- They are Maintainable (no logic, not over specified, state-based, refactored..)
- They are trust-worthy (test the right thing, isolated, not integration tests..)
- Unit Testing just tests the external API of your Unit, you shouldn't test internal behaviour.
- Each test of a TestCase should test one (and only one) method inside this API.
- Aditional Test Cases should be included for failure cases.
- Test the coverage of your tests: Once a unit it's tested, the 100% of the lines inside this unit should had been executed.
Jay Fields has a lot of good advices about writing unit tests and there is a post where he summarize the most important advices. There you will read that you should critically think about your context and judge if the advice is worth to you. You get a ton of amazing answers here, but is up to you decide which is best for your context. Try them and just refactoring if it smells bad to you.
Kind Regards
Never assume that a trivial 2 line method will work. Writing a quick unit test is the only way to prevent the missing null test, misplaced minus sign and/or subtle scoping error from biting you, inevitably when you have even less time to deal with it than now.
I second the "A TRIP" answer, except that tests SHOULD rely on each other!!!
Why?
DRY - Dont Repeat Yourself - applies to testing as well! Test dependencies can help to 1) save setup time, 2) save fixture resources, and 3) pinpoint to failures. Of course, only given that your testing framework supports first-class dependencies. Otherwise, I admit, they are bad.
Follow up http://www.iam.unibe.ch/~scg/Research/JExample/
Often unit tests are based on mock object or mock data. I like to write three kind of unit tests:
- "transient" unit tests: they create their own mock objects/data and test their function with it, but destroy everything and leave no trace (like no data in a test database)
- "persistent" unit test: they test functions within your code creating objects/data that will be needed by more advanced function later on for their own unit test (avoiding for those advanced function to recreate every time their own set of mock objects/data)
- "persistent-based" unit tests: unit tests using mock objects/data that are already there (because created in another unit test session) by the persistent unit tests.
The point is to avoid to replay everything in order to be able to test every functions.
- I run the third kind very often because all mock objects/data are already there.
- I run the second kind whenever my model change.
- I run the first one to check the very basic functions once in a while, to check to basic regressions.
Think about the 2 types of testing and treat them differently - functional testing and performance testing.
Use different inputs and metrics for each. You may need to use different software for each type of test.
I use a consistent test naming convention described by Roy Osherove's Unit Test Naming standards Each method in a given test case class has the following naming style MethodUnderTest_Scenario_ExpectedResult.
-
The first test name section is the name of the method in the system under test.
-
Next is the specific scenario that is being tested.
-
Finally is the results of that scenario.
Each section uses Upper Camel Case and is delimited by a under score.
I have found this useful when I run the test the test are grouped by the name of the method under test. And have a convention allows other developers to understand the test intent.
I also append parameters to the Method name if the method under test have been overloaded.
참고URL : https://stackoverflow.com/questions/61400/what-makes-a-good-unit-test
'Programing' 카테고리의 다른 글
하나의 프로젝트를위한 Android Studio Multi-Windows (0) | 2020.08.23 |
---|---|
오늘 날짜를 mm / dd / yyyy 형식으로 C #으로 가져 오려면 어떻게합니까? (0) | 2020.08.23 |
형식 문자열, 선행 0이있는 정수 (0) | 2020.08.23 |
런타임에 TextView의 스타일을 변경하는 방법 (0) | 2020.08.23 |
MySQL '스키마 만들기'와 '데이터베이스 만들기'-차이점이 있습니까? (0) | 2020.08.22 |