von
Michael Albrecht - Java CAT & PHP Pussy
team neusta GmbH
Vorteile:
Nachteile:
Vorteile:
Nachteile:
Vorteile:
Nachteile:
Vorteile:
Nachteile:
Vorteile:
Nachteile:
Scope | Bug fixing costs |
---|---|
Unit Test | 5€ |
Integration Test | 50€ |
System Test | 500€ |
Customer Test | 5000€ |
Ein Test besteht aus vier Phasen:
package de.mike.tests;
import org.junit.Test;
public class NameDerTestKlasse {
@Test
public void irgendeinBeliebigerMethodenname() {
}
}
...
@BeforeClass
public static void doBeforeAllTests() { ... }
@AfterClass
public static void doAfterAllTests() { ... }
@Before
public void doBeforeEachTest() { ... }
@After
public void doAfterEachTest() { ... }
@Test
public void test1() { ... }
@Test
public void test2() { ... }
...
@BeforeClass - method
@Before - method
Test 1 wird ausgeführt.
@After - method
@Before - method
Test 2 wird ausgeführt.
@After - method
@AfterClass - method
Testmethoden~ und Testklassenspezifische Vor- und Nachbereitungen
Weitervererbung der Test Fixture Methoden
@Category(de.mike.kata.Integrationtest.class)
public class MySpecialIntegrationTest {
...
}
In diesem Fall ist de.mike.kata.Integrationtest ein Marker Interface.
Mit Categories kann man Tests zu Gruppen zusammenfassen und diese Gruppen gezielt ausführen.
import org.junit.Test;
import static org.junit.Assert.assertTrue;
public class SimpleTest {
@Test
public void test() {
assertTrue("Das weiß doch jedes Kind.", 1+1 == 2);
}
}
...
@Test
public void test() {
assertThat(myObj, is(not(nullValue()));
}
...
Code says: "Assert that my object is not a null value."
...
assertThat("Fehler", meinZuPruefenderWert, is(erwarteterWert));
...
Im Fehlerfall kann eine Meldung ausgegeben werden.
@Test
public void testOldAssertions() {
assertEquals(1, 1);
assertNotEquals(1.2345f, 1.2345d, 0.0d);
assertTrue(2 >> 1 == 2 - 1);
assertFalse(11 * 13 == 144);
Integer firstNumber = Integer.valueOf(5);
Integer thirdNumber = Integer.valueOf(5);
Integer secondNumber = new Integer(5);
assertSame(firstNumber, thirdNumber);
assertNotSame(firstNumber, secondNumber);
String word = new String("Hallo");
assertNotSame(word, "Hallo");
assertEquals(word, "Hallo");
assertSame("Hallo Welt!", "Hallo " + "Welt!");
assertArrayEquals(new int[] { 1, 2 }, new int[] { 1, 2 });
assertNotEquals(new int[] { 1, 4 }, new int[] { 1, 3 });
assertNull(null);
assertNotNull(new String());
}
@Test
public void testNewAssertions() {
Integer four = Integer.valueOf(4);
assertThat(four, is(new Integer(4)));
assertThat(four, is(not(Integer.valueOf(5))));
assertThat(four, lessThan(5));
assertThat(four, greaterThanOrEqualTo(4));
assertThat(four, isA(Integer.class));
}
@Test
public void testCombinableMatchers() throws Exception {
Integer four = Integer.valueOf(4);
assertThat(four, either(is(4)).or(is(5)));
assertThat(four, isOneOf(1, 2, 4));
}
@Test
public void testListMatchers() throws Exception {
List numbers = new ArrayList<>();
Integer four = Integer.valueOf(4);
numbers.add(four);
assertThat(numbers, hasItem(four));
assertThat(numbers, not(hasItem(Integer.valueOf(5))));
numbers.add(Integer.valueOf(5));
assertThat(numbers, hasSize(2));
assertThat(numbers, hasItems(5, 4));
assertThat(numbers, contains(4, 5));
assertThat(numbers, not(contains(5, 4)));
}
@Test
public void testJavaBeanProperty() throws Exception {
MyBeanType myBean = new MyBeanType();
assertThat(myBean, hasProperty("test"));
assertThat(myBean, hasProperty("test", is("whatever")));
}
@RunWith(Suite.class)
public class MySuite { ... }
Der JUnit Runner ist die Startklasse für Tests.
In den gängigen IDEs gibt es bereits graphische Testrunner.
Um eigene oder andere Runner zu benutzen verwendet man die @RunWith Annotation
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses({
LoginTest.class,
LogoutTest.class,
NavigationTest.class,
UpdateTest.class
})
public class MyTestSuite {
// leer lassen
}
Es kann bei einem Test immer nur EIN Runner verwendet werden.
Alternativ gibt es die meisten Runner als Rule.
@Test
@Ignore("Test wird zu Testzwecken ausgeblendet.")
public void testSame() {
assertThat(1, is(1));
}
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TestMethodOrder {
@Test
public void testA() { System.out.println("first"); }
@Test
public void testB() { System.out.println("second"); }
@Test
public void testC() { System.out.println("third"); }
}
Seien zwei natürliche Zahlen a und b gegeben.
Wie setzt man so etwas in einem Test um?
@Test(timeout=1000)
public void testWithTimeout() {
...
}
Dieser Test schlägt fehl, wenn er länger als
1 Sekunde (=1000 millisecs) braucht.
Handelt es sich um einen festen Wert,
verwenden wir@DataPoint
Handelt es sich um mehrere Werte (Array),
verwenden wir @DataPoints
Die Zuordnung geschieht über den Parametertyp.
@RunWith(Theories.class)
public class TheoryTest {
@DataPoints
public static Integer[] ints = new Integer[] { -1, 0, 1, 2 };
@Theory
public void testCommutative(final Integer a, final Integer b) {
assertTrue(a + b == b + a);
}
}
Der Test wird für alle Kombination
{(-1,-1), (-1,0), ..., (2,2)} ausgeführt, also 16 mal.
@RunWith(Parameterized.class)
public class LinesOfCodeCounterTest {
private LinesOfCodeCounter counter;
private String code2Test;
private int numberOfCodeLines;
public LinesOfCodeCounterTest(String code,
int numberOfLines,
String comment) {
code2Test = code;
numberOfCodeLines = numberOfLines;
counter = new LinesOfCodeCounter();
}
@Parameters(name = "Test case {index} - {2}")
public static Iterable data1() {
return Arrays.asList(new Object[][] {
{ "public void doIt() {\n}", 2, "No comments" },
{ "// Kommentar\npublic void doIt() {\nSystem.out.println(\"Hello world!\");\n}", 3, "Line comment" },
{ "/* block comment \n second line \n*/\npublic void ...", 1, "Block comment" }
});
}
@Test
public void testSimpleCodeLines() {
assertThat(counter.count(code2Test), is(numberOfCodeLines));
}
}
Manchmal möchte man den gleichen Test mit unterschiedlichen Werten durchlaufen lassen.
In diesen Fällen spricht man von parametrisierten Tests.
JUnit sieht hierfür zwei Lösungen vor:
@RunWith(JUnitParamsRunner.class)
public class LinesOfCodeCounterTest {
@Test
@Parameters(
{ "public void doIt() {\n}, 2, No comments" ,
"// Kommentar\npublic void doIt() {\nSystem.out.println(\"Hello world!\");\n}, 3, Line comment" ,
"/* block comment \n second line \n*/\npublic void ..., 1, Block comment"
}
)
public void testSimpleCodeLines(String code2Test, int numberOfLines, String failureText) {
assertThat(failureText, counter.count(code2Test), is(numberOfLines));
}
}
public RuleType ruleVariable = new RuleType();
Mit Rules lassen sich - ähnlich zu Aspekten in AOP - die Ausführung von Tests vor, nach oder während der Ausführung steuern.
Es gibt 5 Standardrules in JUnit:
public class TemporaryFolderWithRuleTest {
@Rule
public TemporaryFolder folder = new TemporaryFolder();
@Test
public void test() throws Exception {
File file = folder.newFile("test.txt");
assertTrue(file.exists());
}
}
Ich kenne (aktuell) 5 Arten, Exceptions, die geworfen werden können, zu testen:
@Test(expected = IllegalArgumentException.class)
public void testNoNegativeDigits() throws Exception {
obj.callMethodWithWrongParams(1,2,-1);
}
Vorteil: Sehr einfach umzusetzen
Nachteil: Keine Prüfung des Exception Objekts möglich.
...
@Test
public void testNoNegativeDigits() throws Exception {
try {
obj.callMethodWithWrongParams(1,2,-1);
fail("IllegalArgumentException should have been thrown.");
} catch (IllegalArgumentException ex) {
assertThat(ex.getMessage(), is("Only digits [1..9] are allowed."));
}
...
}
Vorteil: Analoges Handling im Code
Nachteil: Keine Coverage Berechnung mit JaCoCo möglich.
@Test
public void testNoNegativeDigits() throws Exception {
catchException(obj).callMethodWithWrongParams(1,2,-1);
assertThat(caughtException(),
is(instanceOf(IllegalArgumentException.class)));
}
Vorteil: 100%ige Code Coverage mit JaCoCo
Nachteil: Extra Bibliothek.
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Test
public void testNoNegativeDigits() throws Exception {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Only digits [1..9] are allowed.");
obj.callMethodWithWrongParams(1,2,-1);
}
Vorteil: Sehr elegante Umsetzung ohne try...catch
Nachteil: Keine Coverage Berechnung mit JaCoCo möglich.
@Test
public void testNoNegativeDigits() throws Exception {
catchException(obj).callMethodWithWrongParams(1,2,-1);
assertThat(caughtException(),
is(instanceOf(IllegalArgumentException.class)));
}
Vorteil: 100%ige Code Coverage mit JaCoCo
Nachteil: Extra Bibliothek.
composer require --dev phpunit/phpunit
Um auf phpunit zugreifen zu können, muss die Abhängigkeit per composer geladen werden:
{
...
"autoload-dev": {
"psr-4": {
"Your\\Basic\\Namespace\\Tests\\": "tests/"
}
},
...
}
Des Weiteren empfiehlt sich der Eintrag für die Namespace Deklaration in Tests (in composer.json):
<?php declare(strict_types=1);
namespace Kata\Tests\Kata;
use PHPUnit\Framework\TestCase;
class MyClassToCheckTest extends TestCase
{
public function test_check_regular_case(): void
{
// Test Fixture
// Test Execution
// Test Assertions & Verifications
}
}
<?php declare(strict_types=1);
namespace Kata\Tests\Kata;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
class MyClassToCheckTest extends TestCase
{
#[Test]
public function check_regular_case(): void
{
}
}
class MyClassToCheckTest extends TestCase
{
public static function setUpBeforeClass(): void {}
protected function setUp(): void {}
#[Test]
public function check_regular_case(): void {}
protected function tearDown(): void {}
public static function tearDownAfterClass(): void {}
}
php vendor/bin/phpunit [several_options]
via Konsole:
via IDE:
via Konsole:
via IDE:
Start
1
2
3
4
5
Stop
Anzahl offener
Bugs
Aufwand
Test First
„…cause test last does not work.“
Known interfaces
TDD ist keine Entwurfstechnik
Keine Tests = kein Refactoring
Einsturz des Kartenhaus
Software Entwicklung ist Gartenpflege
"Unkraut wird nicht gesät, muss aber
entfernt werden."
Entropie Prinzip
Mocks bedeutet auf deutsch Attrappe
Na?!
Wieviele waren es?
Bitte achten Sie auf die rechte Seite und zählen alle Code Änderungen
Finde die Mutanten
...
und töte sie!!!
Extension für EasyMock und Mockito
Support für JUnit und TestNG
Powermock | Mockito | JUnit |
---|---|---|
1.5.6 | 1.9.5 | 4.0 - 4.11 |
1.6.2 - 2.0 | 1.10.* | >= 4.0 |
1.7.* | 2.8.* | >= 4.0 |
<properties>
<powermock.version>1.7.1</powermock.version>
</properties>
<dependencies>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
Um in Maven Powermock unter JUnit zu verwenden, braucht es folgende Dependency:
@RunWith(PowerMockRunner.class)
@PrepareForTest( { YourClassWithStaticMethod.class })
public class YourTestCase {
...
}
Um einen einfachen Test mit Powermock durchzuführen, benötigt man folgendes Grundgerüst:
@Test
public void testChangingInternalState() throws Exception {
TypeWithReadonlyField sut = Whitebox.invokeConstructor(TypeWithReadonlyField.class);
assertThat(sut.getValue(), is("internal"));
Whitebox.setInternalState(sut, "value", "different");
assertThat(sut.getValue(), is("different"));
}
Mit Hilfe der Klasse org.powermock.reflect.Whitebox lassen sich:
// Beispiel für Methodenunterdrückung
@Test
public void testSuppressMethod() throws Exception {
...
suppress(method(ExampleWithEvilMethod.class, "getEvilMessage"));
...
}
Mit Powermock lassen sich unterschiedliche Dinge unterdrücken, deren Ausführung zu Laufzeitproblemen in Tests kommen können:
@RunWith(PowerMockRunner.class)
@PrepareForTest({ System.class })
public class MockSystemClassesTest {
@Test
public void assertThatMockingOfNonFinalSystemClassesWorks() {
mockStatic(System.class);
when(System.currentTimeMillis()).thenReturn(0L);
assertThat(System.currentTimeMillis(), is(0L));
}
}
package powermock;
public class ClassWithStaticMethods {
public static int returnOne() {
return 1;
}
public static int returnTwo() {
return 2;
}
public static int returnThree() {
return 3;
}
}
package powermock;
import static org.hamcrest.CoreMatchers.is;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.when;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import static org.junit.Assert.assertThat;
@RunWith(PowerMockRunner.class)
@PrepareForTest(ClassWithStaticMethods.class)
public class ClassCallingStaticMethodsTest {
@Test
public void testMethod() throws Exception {
mockStatic(ClassWithStaticMethods.class);
when(ClassWithStaticMethods.returnOne()).thenReturn(100);
when(ClassWithStaticMethods.returnTwo()).thenReturn(200);
when(ClassWithStaticMethods.returnThree()).thenReturn(300);
assertThat(new ClassCallingStaticMethods().sumOfAllCalls(), is(600));
}
}